//
//  MMCapture.m
//  MemeMaker
//
//  Created by dangthan on 7/30/13.
//  Copyright (c) 2013 soyo. All rights reserved.
//

#import "MMCapture.h"

@interface MMCapture (Internal)

- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position;
- (AVCaptureDevice *)frontFacingCamera;
- (AVCaptureDevice *)backFacingCamera;

@end

@implementation MMCapture
@synthesize vInput, capSession;
@synthesize flash, focus, exposure, torch, whiteBalance;
@synthesize delegate;

- (id) init {
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(captureSessionDidStartRunning:)
                                                     name:AVCaptureSessionDidStartRunningNotification
                                                   object:capSession];
    }
    return self;
}

//do not call super dealloc
- (void) dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [capSession stopRunning];
}

- (BOOL) cameraToggleClicked {
    BOOL success = NO;
    if ([self hasMultipleCameras]) { //working with multi camera in device
        NSError *err;
        AVCaptureDeviceInput *aDeviceInput;
        AVCaptureDevicePosition position = vInput.device.position;
        if (position == AVCaptureDevicePositionBack) {
            aDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self frontFacingCamera] error:&err];
        } else if (position == AVCaptureDevicePositionFront) {
            aDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backFacingCamera] error:&err];
        } else {
            goto ntx;
        }
        
        if (aDeviceInput != nil) {
            [capSession beginConfiguration];
            [capSession removeInput:vInput];
            if ([capSession canAddInput:aDeviceInput]) {
                [capSession addInput:aDeviceInput];
                vInput = aDeviceInput;
            } else {
                [capSession addInput:vInput];
            }
            [capSession commitConfiguration];
            success = YES;
        } else if (err) {
            if (delegate && [delegate respondsToSelector:@selector(someOtherError:)]) {
                [delegate someOtherError:err];
            }
        }
    }
ntx:
    return success;
}

- (BOOL) hasMultipleCameras {
    return [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count] > 1 ? YES : NO;
}

- (void) startRunning {
    [capSession startRunning];
}

- (void) stopRunning {
    if ([capSession isRunning]) {
        [capSession stopRunning];
    }
}

#pragma mark - Exposure
- (BOOL) hasExposure {
    AVCaptureDevice *device = vInput.device;
    return [device isExposureModeSupported:AVCaptureExposureModeLocked] || [device isExposureModeSupported:AVCaptureExposureModeAutoExpose] || [device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure];
}

- (AVCaptureExposureMode) exposure {
    return vInput.device.exposureMode;
}

- (void) setExposure:(AVCaptureExposureMode)exposureMode{
    if (exposure == AVCaptureExposureModeAutoExpose) {
        exposure = AVCaptureExposureModeContinuousAutoExposure;
    }
    AVCaptureDevice *device = vInput.device;
    if ([device isExposureModeSupported:exposureMode] && device.exposureMode != exposureMode) {
        NSError *err;
        if ([device lockForConfiguration:&err]) {
            device.exposureMode = exposureMode;
            [device unlockForConfiguration];
        } else {
            if (delegate && [delegate respondsToSelector:@selector(acquiringDeviceLockFailedWithError:)]) {
                [delegate acquiringDeviceLockFailedWithError:err];
            }
        }
    }
}

#pragma mark - Flash
- (BOOL) hasFlash {
    return vInput.device.hasFlash;
}

- (AVCaptureFlashMode) flash {
    return vInput.device.flashMode;
}

- (void) setFlash:(AVCaptureFlashMode)flashMode {
    AVCaptureDevice *device = vInput.device;
    if ([device isFlashModeSupported:flashMode] && device.flashMode != flashMode) {
        NSError *err;
        if ([device lockForConfiguration:&err]) {
            device.flashMode = flashMode;
            [device unlockForConfiguration];
        } else {
            if (delegate && [delegate respondsToSelector:@selector(acquiringDeviceLockFailedWithError:)]) {
                [delegate acquiringDeviceLockFailedWithError:err];
            }
        }
    }
}

#pragma mark - Focus
- (BOOL) hasFocus {
    AVCaptureDevice *device = vInput.device;
    return [device isFocusModeSupported:AVCaptureFocusModeLocked] || [device isFocusModeSupported:AVCaptureFocusModeAutoFocus] || [device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus];
}

- (AVCaptureFocusMode) focus {
    return vInput.device.focusMode;
}

- (void) setFocus:(AVCaptureFocusMode)focusMode {
    AVCaptureDevice *device = vInput.device;
    if ([device isFocusModeSupported:focusMode] && device.focusMode != focusMode) {
        NSError *err;
        if ([device lockForConfiguration:&err]) {
            device.focusMode = focusMode;
            [device unlockForConfiguration];
        } else {
            if (delegate && [delegate respondsToSelector:@selector(acquiringDeviceLockFailedWithError:)]) {
                [delegate acquiringDeviceLockFailedWithError:err];
            }
        }
    }
}

#pragma mark - Torch
- (BOOL) hasTorch {
    return vInput.device.hasTorch;
}

- (AVCaptureTorchMode) torch {
    return vInput.device.torchMode;
}

- (void) setTorch:(AVCaptureTorchMode)torchMode {
    AVCaptureDevice *device = vInput.device;
    if ([device isTorchModeSupported:torchMode] && device.torchMode != torchMode) {
        NSError *err;
        if ([device lockForConfiguration:&err]) {
            device.torchMode = torchMode;
            [device unlockForConfiguration];
        } else {
            if (delegate && [delegate respondsToSelector:@selector(acquiringDeviceLockFailedWithError:)]) {
                [delegate acquiringDeviceLockFailedWithError:err];
            }
        }
    }
}

#pragma mark - WhiteBalance
- (BOOL) hasWhiteBalance {
    AVCaptureDevice *device = vInput.device;
    return [device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeLocked] || [device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeAutoWhiteBalance];
}

- (AVCaptureWhiteBalanceMode) whiteBalance {
    return vInput.device.whiteBalanceMode;
}

- (void) setWhiteBalance:(AVCaptureWhiteBalanceMode)whiteBalanceMode{
    if (whiteBalance == AVCaptureWhiteBalanceModeAutoWhiteBalance) {
        whiteBalance = AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance;
    }
    AVCaptureDevice *device = vInput.device;
    if ([device isWhiteBalanceModeSupported:whiteBalanceMode] && device.whiteBalanceMode != whiteBalanceMode) {
        NSError *err;
        if ([device lockForConfiguration:&err]) {
            device.whiteBalanceMode = whiteBalanceMode;
            [device unlockForConfiguration];
        } else {
            if (delegate && [delegate respondsToSelector:@selector(acquiringDeviceLockFailedWithError:)]) {
                [delegate acquiringDeviceLockFailedWithError:err];
            }
        }
    }
}



- (BOOL) setupSessionWithPreset:(NSString *)sessionPreset error:(NSError *__autoreleasing *)error {
    vInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backFacingCamera] error:error];
    
    stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
    stillImageOutput.outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:AVVideoCodecJPEG, AVVideoCodecKey, nil];
    
    capSession = [[AVCaptureSession alloc] init];
    if ([capSession canAddInput:vInput]) {
        [capSession addInput:vInput];
    }
    if ([capSession canAddOutput:stillImageOutput]) {
        [capSession addOutput:stillImageOutput];
    }
    
    [capSession setSessionPreset:sessionPreset];
    
    return YES;
}

- (void) focusAtPoint:(CGPoint)point {
    AVCaptureDevice *device = vInput.device;
    if (device.isFocusPointOfInterestSupported && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
        NSError *error;
        if ([device lockForConfiguration:&error]) {
            device.focusPointOfInterest = point;
            device.focusMode = AVCaptureFocusModeAutoFocus;
            [device unlockForConfiguration];
        } else {
            if ([delegate respondsToSelector:@selector(acquiringDeviceLockFailedWithError:)]) {
                [delegate acquiringDeviceLockFailedWithError:error];
            }
        }
    }
}

- (void) exposureAtPoint:(CGPoint)point {
    AVCaptureDevice *device = vInput.device;
    if (device.isExposurePointOfInterestSupported && [device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
        NSError *error;
        if ([device lockForConfiguration:&error]) {
            device.exposurePointOfInterest = point;
            device.exposureMode = AVCaptureExposureModeContinuousAutoExposure;
            [device unlockForConfiguration];
        } else {
            if ([delegate respondsToSelector:@selector(acquiringDeviceLockFailedWithError:)]) {
                [delegate acquiringDeviceLockFailedWithError:error];
            }
        }
    }

}

- (void) captureStillImage {
    AVCaptureConnection *connection = [MMCapture connectionWithMediaType:AVMediaTypeVideo fromConnections:stillImageOutput.connections];
    if ([connection isVideoOrientationSupported]) {
        [connection setVideoOrientation:AVCaptureVideoOrientationPortrait];//change for rotation
    }
    [stillImageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *err) {
        [capSession stopRunning];
        if (imageDataSampleBuffer != NULL) {
            NSData *imgData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
            UIImage *img = [UIImage imageWithData:imgData];
            if (delegate && [delegate respondsToSelector:@selector(captureStillImageFinished:)]) {
                [delegate captureStillImageFinished:img];
            }
        } else if (err) {
            if (delegate && [delegate respondsToSelector:@selector(captureStillImageFailedWithError:)]) {
                [delegate captureStillImageFailedWithError:err];
            }
        }
    }];
}

- (NSUInteger)cameraCount {
    return [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count];
}

+ (AVCaptureConnection *) connectionWithMediaType:(NSString *)mediaType fromConnections:(NSArray *)connections {
    for (AVCaptureConnection *connection in connections) {
        for (AVCaptureInputPort *port in [connection inputPorts]) {
            if ([port.mediaType isEqual:mediaType]) {
                return connection;
            }
        }
    }
    return nil;
}

- (void)captureSessionDidStartRunning:(NSNotification *)note {
    if ([delegate respondsToSelector:@selector(captureSessionDidStartRunning)]) {
        [delegate captureSessionDidStartRunning];
    }
}

@end

@implementation MMCapture (Internal)

- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position {
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *device in devices) {
        if ([device position] == position) {
            return device;
        }
    }
    return nil;
}

- (AVCaptureDevice *)frontFacingCamera {
    return [self cameraWithPosition:AVCaptureDevicePositionFront];
}

- (AVCaptureDevice *)backFacingCamera {
    return [self cameraWithPosition:AVCaptureDevicePositionBack];
}

@end
